
/**
 * @fileoverview Provides the core JavaScript functionality for the R/C Fly spot
 *   application.
 */
 
(function($) {

  var map = null;
  var placesService = null;
  var user = null;
  var createdSpot = null;
  var eviction_list = [];

	var document = window.document,
		$ = jQuery,
		g = google.maps;

    window.spots = {};

	/**
	* Create the context menu
	*
	* @param array opts Array of options.
	* @todo Menu id needs to be unique, in case of multiple maps and context menus
	*/
	var contextMenu = function(opts)
	{
		// A way to access 'this' object from inside functions
		var self = this;

		if ( opts.map !== undefined )
		{
			// Put the map onto the object
			this.theMap = opts.map;

			// Keep track of where you clicked, for the callback functions.
			this.clickedLatLng = null;

			// Create the context menu element
			this.theMenu = $(document.createElement('div'))
				.attr('class', 'contextMenu')
	
			// .. disable the browser context menu on our context menu
			.bind('contextmenu', function() { return false; })

			// .. append a ul list element
			.append($(document.createElement('ul')))

			// .. then append it to the map object
			.appendTo(this.theMap.getDiv());

			// Display and position the menu
			g.event.addListener(this.theMap, 'rightclick', function(e)
			{
				// Shorthand some stuff
				var mapDiv = $(self.theMap.getDiv()),
				menu = self.theMenu,
				x = e.pixel.x,
				y = e.pixel.y;

				// Hide the context menu if its open
				menu.hide();

				// Save the clicked location
				self.clickedLatLng = e.latLng;

				// Adjust the menu if clicked to close to the edge of the map
				if ( x > mapDiv.width() - menu.width() )
					x -= menu.width();

				if ( y > mapDiv.height() - menu.height() )
					y -= menu.height();

				// Set the location and fade in the context menu
				menu.css({ top: y, left: x }).fadeIn(200);
			});

			// Hide context menu on several events
			$.each('click dragstart zoom_changed maptypeid_changed center_changed'.split(' '), function(i,name){
				g.event.addListener(self.theMap, name, function(){ self.theMenu.hide(); });
			});
		}
	}

	/**
	* Add new items to the context menu
	*
	* @param string name Name of the list item.
	* @param function callback Function to run when you click the list item
	* @return jQuery The list item that is created.
	*/
	contextMenu.prototype.addItem = function(name, loc, callback)
	{
		// If no loc was provided
		if ( typeof loc === 'function')
		{
			callback = loc;
			loc = undefined;
		}

		// A way to access 'this' object from inside functions
		var self = this,

			// The name turned into camelCase for use in the li id, and anchor href
			idName = name.toCamel(),

			// The li element
			li = $(document.createElement('li'))
				.attr('id', idName);

				// the anchor element
				$(document.createElement('a'))
				.attr('href', '#'+idName).html(name)
				.appendTo(li)

				// Add some nice hover effects
				//.hover(function() {
				//	$(this).parent().toggleClass('hover');
				//})

				// Set the click event
				.click(function(){

					// fade out the menu
					self.theMenu.hide();

					// call the callback function - 'this' would refer back to the jQuery object of the item element
					callback.call(this, self.theMap, self.clickedLatLng);

					// make sure the click doesnt take us anywhere
					return false;
		});

		// If `loc` is a number put it at that location
		if ( typeof loc === 'number' && loc < this.theMenu.find('li').length)
			this.theMenu.find('li').eq(loc).before(li);

		// .. else appened it to the end of the menu
		else
			this.theMenu.find('ul').append(li);

		// Return the whole list item
		return li;
	};

	/**
	* Add a seperators
	*
	* @return jQuery The list item that is created.
	*/
	contextMenu.prototype.addSep = function(loc)
	{
		// Create the li element
		var li = $(document.createElement('li'))
			.addClass('separator')

			// .. add a div child
			.append($(document.createElement('div')))

		// If loc is a number put the li at that location
		if ( typeof loc === 'number' )
			this.theMenu.find('li').eq(loc).before(li)

			// .. else appened it to the end
		else
			this.theMenu.find('ul').append(li);

		// Return the li element
		return li
	};

	/**
	* Remove a menu list item.
	*
	* @param string name The string used to create the list item.
	* @param number name The index value of the list item.
	* @param jQuery name The jQuery object that is returned by addItem() or addSep()
	*/
	contextMenu.prototype.remove = function(item)
	{
		// No need to search for name if its a jquery object
		if ( item instanceof $ )
			item.remove();

		// Find all the elements and remove the one at the specified index
		else if ( typeof item === 'number' )
			this.theMenu.find('li').eq(item).remove()

		// Find all the items by the id name and remove them
		else if ( typeof item === 'string' )
		{
			// Find and remove the element
			this.theMenu.find('#'+item.toCamel()).remove()
		}
	};

	// Expose this to the global object
	window.contextMenu = contextMenu;

	/**
	* Convert a string into a 'camelCase' string
	*
	* @example 'Camel case string'.toCamel() -> 'camelCaseString'
	*/
	String.prototype.toCamel = function() {
		return this.toLowerCase().replace(/(\s)([a-z])/gi, function(match, group1, group2){
		return group2.toUpperCase().replace(group1,'');
	});
	}

  /**
   * Represents each fly spot within R/C fly spot.
   * @param {spotname} The geocode name of the spot.
   * @param {string} experience The experience at the given spot
   * @param {number} latitude The spot's latitude.
   * @param {number} longitude The spot's longitude.
   * @param {link} an optional video link.
   * @param {rating} a rating for the spot.
   * @param {geohash} the geo hash for the spot.
   * @constructor
   */
  var Flyspot = function( experience, lat, lng, link, rating, geohash, map) {

    var me = this;
    //window.spots[geohash] = this;

    this.spotname = 'New Fly spot';
    this.experience = experience;
    this.latLng = new google.maps.LatLng(lat, lng);
	this.link = link;
	this.rating = rating;
	this.geohash = geohash;

	// To add the marker to the map, call setMap();
	this.marker = new google.maps.Marker({
		  position: this.latLng, 
		  map: map,
		  title: this.spotname
	 });
     eviction_list.push( this.marker );

	//this.marker.setTitle( spotname );

	var infowindow = new google.maps.InfoWindow(
		  { content: experience,
		    size: new google.maps.Size(50,50)
	 });

	google.maps.event.addListener( this.marker, 'click', function() {
		infowindow.open(map, me.marker);
	});
	infowindow.open(map, me.marker);

	searchPlace( { location: this.latLng, radius: '500'}, map, this, this.marker, document.getElementById('spotname') );

	//var spotname = $('#log-entries');
	//var spotname = $('#log-entries');
	//var spotname = $('#log-entries');
	//$('#log-entries');
	//$('#log-entries');

  };

  Flyspot.prototype.remove = function( map ) {
	this.marker.setMap( null );
	this.marker = null;
  };

  var searchPlace = function(request, map, flyspot, marker, listbox, searchAgain ) {  
	  var hash = '';
	  
	   if ( searchAgain === undefined ) {
			searchAgain = true;
	   }

	  if (request.latLng) {
		hash = 'q=' + request.latLng.toUrlValue(6);
	  } else {
		hash = 'q=' + request.address;
	  }

	 while ( listbox.selectedIndex > -1 ) {
		listbox.remove( listbox.selectedIndex );
	 } 

	  hashFragment = '#' + escape(hash);
	  placesService.search(request, function( results, status ) {
		  if (! results) {
			alert("Place search did not return a valid response");
		  } else {
			if (status == google.maps.places.PlacesServiceStatus.OK) {
				for ( var i = 0; i < results.length; i++ ) {
				    if ( results[ i ].types.indexOf( 'political' ) != -1 ) {
				      continue;
                    } 
			        var opt = document.createElement("option");
			        // Assign text and value to Option object
			        opt.text = results[ i ].name;
			        opt.value = results[ i ].name;
		
		        	// Add an Option object to Drop Down/List Box
		        	listbox.options.add(opt);

				    marker.setTitle( results[0].name );
				}
				changedList( listbox );
			}
			if ( searchAgain && ( listbox.options.length < 1 ) ) {
				searchPlace( { bounds: map.getBounds()}, map, this, this.marker, document.getElementById('spotname'), false );
			}
		  }		
	  } );
  }

  /**
   * Performs basic scrubbing of input to prevent HTML injection.
   * @param {string} input The input text to sanitize.
   * @return {string} The sanitized input.
   */
  var sanitizeInput = function(input) {
    return input.replace('&', '&amp;').replace('<', '&lt;').
        replace('>', '&gt;');
  };
  
  /**
   * Causes a chat event for the currently active user, including an Ajax
   * call against the R/C fly spot datastore.
   * @param {DOM} chatInput The input field to pull chat contents from.
   */
  window.saveSpot = function( spotname, bettername, cb, experience ) {
    var selected = spotname.options[ spotname.selectedIndex ].value;

	if ( cb.checked ) {
		$.post('/spotevent/save', {
		  'spotname': bettername.value,
		  'experience': experience.value,
		  'latitude': createdSpot.latLng.lat(),
		  'longitude': createdSpot.latLng.lng()
		  });
	} else {
		$.post('/spotevent/save', {
		  'spotname': selected,
		  'experience': experience.value,
		  'latitude': createdSpot.latLng.lat(),
		  'longitude': createdSpot.latLng.lng()
		  });
	}
  };

  window.betterClick = function( checkbox ) {
	 var bettername = document.getElementById( 'bettername' );
	 var spotname = document.getElementById( 'spotname' );
	 var selected = spotname.options[ spotname.selectedIndex ].value;

	 if ( checkbox.checked ) {
		bettername.disabled = false;
	 } else {
		bettername.value = selected;
		bettername.disabled = true;
	 }
  };

  window.changedList = function( listbox ) {
	 var bettername = document.getElementById( 'bettername' );
 	var selected = "";
	 if ( listbox.selectedIndex > -1 ) {
		 selected = listbox.options[ listbox.selectedIndex ].value;
	 } 
	 var checkbox = document.getElementById( 'better_enable' );	 		 

	 checkbox.checked = false;
	 bettername.value = selected; 
  };

  window.spotsCallback = function(data) {
    var spots = data.spots;

    for (var i = 0; i < spots.length; ++i) {
	   	var spot = spots[ i ];
		new Flyspot( spot.experience, spot.lat, spot.lon, spot.videolink, spot.rating, spot.geohash, map );
    }    
  };

  /**
   * A callback for when an update request succeeds.
   * @param {string} json JSON data to be evaluated and passed on to event
   *   callbacks.
   */
  window.updateSuccess = function(json) {
     var data = eval('(' + json + ')');
     spotsCallback(data);
  }
  
  /**
   * A callback for when updates fail. Presents an error to the user and
   * forces a lengthier delay between updates.
   */
  window.updateError = function() {
     alert('An update error occured! Trying again in a bit.');
     //window.setTimeout(update, GEOCHAT_VARS['error_interval'])
  }

  /**
   * The main update handler, used to query the Geochat DB and cache for
   * updated events. Successful queries pass the results on to the
   * chatCallback and moveCallback functions.
   */
  window.update = function() {
    var latlng = map.getCenter();
    evictMarkers();

    $.ajax({
      type: 'GET',
      url: '/spotevent/update',
      data: [
        'lat=', latlng.lat(),
        '&lon=', latlng.lng(),
        '&zoom=', map.getZoom()
      ].join(''),
      success: updateSuccess,
      error: updateError
    });
  }

  var evictMarkers = function() {

    // clear all markers
    $(eviction_list).each(function () {
         this.set_map(null);
    });
    // reset the eviction array 
    eviction_list = [];
  }  
  
  window.onload = function() {
      // Initialize the map
      // var mapDiv = document.getElementById('map');

	  var myLatlng = new google.maps.LatLng( 37.273684,-115.79833 );
	  var myOptions = {
		zoom: 13,
		center: myLatlng,
		mapTypeId: google.maps.MapTypeId.SATELLITE,
	  }
	  map = new google.maps.Map(document.getElementById('map'), myOptions);
		
		placesService = service = new google.maps.places.PlacesService(map);

		/**
		* Create the menu and attached it to the map
		*/
		var menu = new contextMenu({map:map});

		// Add some items to the menu
		menu.addItem('Zoom In', function(map, latLng){
			map.setZoom( map.getZoom() + 1);
			map.panTo( latLng );
		});

		menu.addItem('Zoom Out', function(map, latLng){
			map.setZoom( map.getZoom() - 1 );
			map.panTo(latLng);
		});

		menu.addItem('Center Here', function(map, latLng){
			map.panTo(latLng);
		});

		g.event.addListener( map, 'zoom_changed', function() {
			update();
		} );
		g.event.addListener( map, 'dragend', function() {
			update();
		} );

		if (FLYSPOT_VARS['user_nickname'] ) {
			menu.addSep();
			menu.addItem( 'Add Fly spot', function(map, latLng) {
				if ( createdSpot ) {
					createdSpot.remove();
					createdSpot = null;
				}
				createdSpot = new Flyspot( 'What was it like flying here?', 
					latLng.lat(), latLng.lng(), 'http://your_link_here', 3, 'abcde', map);
			});
		}
  };

})(jQuery);
